//===============================================================================
// Copyright (C) 2008 Charles River Media. All rights reserved.
// 
// Catmull-Clark subdivision surface viewer program
//
// The developer of the code is
// Gyorgy Antal <gyantal@gmail.com>
//
// Contributor(s):
// Laszlo Szirmay-Kalos <szirmay@iit.bme.hu>
//
// The software was developed for the chapter 
// "Fast Evaluation of Subdivision Surfaces on DirectX10 Graphics Hardware"
// of the book ShaderX 6: Advanced Rendering Techniques, 
// edited by Wolfgang Engel,
// the appropriate section edited by Tom Forsyth. 
//
// The files are provided without support, instruction, or implied warranty of any
// kind.  Charles River Media makes no guarantee of its fitness for a particular purpose and is
// not liable under any circumstances for any damages or loss whatsoever arising
// from the use or inability to use this file or items derived from it.
//
// The source code can be found in the RenderVista project: 
// http://code.google.com/p/rendervista/source
// More specifically on this site:
// http://rendervista.googlecode.com/svn/trunk/Projects/DX10Subdivision/
//===============================================================================
// File: DXFramework.cpp
//
// Description:
// This is a general DirectX 10 framework that creates a window and the DX device.
// It tries to substitute the framework found in the DX10 SDK
//===============================================================================

#include <windows.h>
#include <stdio.h>
#include "DXFramework.h"
#include "resource.h"
#include "GSSubdivider.h"
#include "..\Base\\Base.h"


//--------------------------------------------------------------------------------------
// Global Variables
//--------------------------------------------------------------------------------------
HWND                                g_hWnd = NULL;
ID3D10Device*                       g_pd3dDevice = NULL;
IDXGISwapChain*                     g_pSwapChain = NULL;
ID3D10RenderTargetView*             g_pRenderTargetView = NULL;
ID3D10DepthStencilView*				g_pDepthStencilView = NULL;
ID3D10Texture2D*					g_pDepthStencil = NULL;
ID3DX10Font*						g_pFont = NULL;         // Font for drawing text
UINT								g_fpsRenderedFrames = 0;
double								g_fpsLastUpdateTime = 0.0;
double								g_fps = 0.0;
LONGLONG							g_llQPFTicksPerSec;


D3D10_DRIVER_TYPE                   g_driverType = D3D10_DRIVER_TYPE_NULL;
   
ID3D10RasterizerState*				g_wireframeRasterizerState;
ID3D10RasterizerState*				g_solidRasterizerState;
bool								g_isWireFrame = false;

//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
HRESULT             InitWindow(HINSTANCE hInstance, int nCmdShow);
HRESULT             InitDevice();
void                CleanupDevice();
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
void                RenderFramework();



	


//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing 
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	if(FAILED(InitWindow(hInstance, nCmdShow)))
		return 0;

	if(FAILED(InitDevice()))
	{
		CleanupDevice();
		return 0;
	}


	RECT rc;
	GetClientRect(g_hWnd, &rc);
	UINT width = rc.right - rc.left;
	UINT height = rc.bottom - rc.top;
	InitAfterDeviceCreation(width, height);

	// Main message loop
	MSG msg = {0};
	while(WM_QUIT != msg.message)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			RenderFramework();  
		}
	}

	CleanupDevice();

	return (int) msg.wParam;
}


//--------------------------------------------------------------------------------------
// Register class and create window
//--------------------------------------------------------------------------------------
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
	// Register class
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX); 
	wcex.style          = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc    = WndProc;
	wcex.cbClsExtra     = 0;
	wcex.cbWndExtra     = 0;
	wcex.hInstance      = hInstance;
	wcex.hIcon          = LoadIcon(hInstance, (LPCTSTR)IDI_GSSUBDIVIDER);
	wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName   = NULL;
	wcex.lpszClassName  = L"GSSubdividerWindowClass";
	wcex.hIconSm        = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_GSSUBDIVIDER);
	if(!RegisterClassEx(&wcex))
		return E_FAIL;

	// Create window
	//RECT rc = { 0, 0, 640, 480 };
	RECT rc = { 0, 0, 960, 600 };
	//RECT rc = { 0, 0, 1440, 960};
	AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
	g_hWnd = CreateWindow(L"GSSubdividerWindowClass", L"Direct3D 10 GS Subdivision - level 2", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL);
	if(!g_hWnd)
		return E_FAIL;

	ShowWindow(g_hWnd, nCmdShow);


	// Use QueryPerformanceFrequency to get the frequency of the counter
	LARGE_INTEGER qwTicksPerSec;
	QueryPerformanceFrequency(&qwTicksPerSec);
	g_llQPFTicksPerSec = qwTicksPerSec.QuadPart;

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Create Direct3D device and swap chain
//--------------------------------------------------------------------------------------
HRESULT InitDevice()
{
	HRESULT hr = S_OK;

	RECT rc;
	GetClientRect(g_hWnd, &rc);
	UINT width = rc.right - rc.left;
	UINT height = rc.bottom - rc.top;

	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

	D3D10_DRIVER_TYPE driverTypes[] = 
	{
		D3D10_DRIVER_TYPE_HARDWARE,
		D3D10_DRIVER_TYPE_REFERENCE,
	};
	UINT numDriverTypes = sizeof(driverTypes) / sizeof(driverTypes[0]);

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferCount = 1;
	sd.BufferDesc.Width = width;
	sd.BufferDesc.Height = height;
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = g_hWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	for(UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
	{
		g_driverType = driverTypes[driverTypeIndex];
		hr = D3D10CreateDeviceAndSwapChain(NULL, g_driverType, NULL, createDeviceFlags, 
			D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice);
		if(SUCCEEDED(hr))
			break;
	}
	if(FAILED(hr))
		return hr;

	//CComPtr< ID3D10Debug > pD3D10Debug;
	//hr = g_pd3dDevice->QueryInterface(__uuidof(ID3D10Debug), reinterpret_cast< void** >(&pD3D10Debug));
	ID3D10Debug* pD3D10Debug;
	hr = g_pd3dDevice->QueryInterface(__uuidof(ID3D10Debug), reinterpret_cast< void** >(&pD3D10Debug));

	// Create a render target view
	ID3D10Texture2D *pBuffer;
	hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBuffer);
	if(FAILED(hr))
		return hr;

	hr = g_pd3dDevice->CreateRenderTargetView(pBuffer, NULL, &g_pRenderTargetView);
	pBuffer->Release();
	if(FAILED(hr))
		return hr;

	// Create depth stencil texture
	D3D10_TEXTURE2D_DESC descDepth;
	descDepth.Width = width;
	descDepth.Height = height;
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D32_FLOAT;
	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	descDepth.Usage = D3D10_USAGE_DEFAULT;
	descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MiscFlags = 0;
	hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencil);
	if(FAILED(hr))
		return hr;

	// Create the depth stencil view
	D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	hr = g_pd3dDevice->CreateDepthStencilView(g_pDepthStencil, &descDSV, &g_pDepthStencilView);
	if(FAILED(hr))
		return hr;


	g_pd3dDevice->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);

	// Setup the viewport
	D3D10_VIEWPORT vp;
	vp.Width = width;
	vp.Height = height;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	g_pd3dDevice->RSSetViewports(1, &vp);

	// Set primitive topology
	g_pd3dDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);


	D3DX10CreateFont(g_pd3dDevice, 15, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 
		DEFAULT_PITCH | FF_DONTCARE, L"Arial", &g_pFont);

	//// Initialize the world matrices
	//D3DXMatrixIdentity(&g_World);

	//// Initialize the view matrix
	//D3DXVECTOR3 Eye(0.0f, 3.0f, -6.0f);
	//D3DXVECTOR3 At(0.0f, 1.0f, 0.0f);
	//D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);
	//D3DXMatrixLookAtLH(&g_View, &Eye, &At, &Up);

	//// Initialize the projection matrix
	//D3DXMatrixPerspectiveFovLH(&g_Projection, (float)D3DX_PI * 0.25f, width/(FLOAT)height, 0.1f, 100.0f);


	D3D10_RASTERIZER_DESC rasterizerStateDesc;
	rasterizerStateDesc.FillMode = D3D10_FILL_WIREFRAME;
	//rasterizerStateDesc.CullMode = D3D10_CULL_BACK;
	rasterizerStateDesc.CullMode = D3D10_CULL_NONE;
	rasterizerStateDesc.FrontCounterClockwise = false;
	rasterizerStateDesc.DepthBias = 0;
	rasterizerStateDesc.DepthBiasClamp =  0.0f;
	rasterizerStateDesc.SlopeScaledDepthBias = 0.0f;
	rasterizerStateDesc.DepthClipEnable = true;
	rasterizerStateDesc.ScissorEnable = false;
	rasterizerStateDesc.MultisampleEnable = false;
	rasterizerStateDesc.AntialiasedLineEnable = false;
	g_pd3dDevice->CreateRasterizerState(&rasterizerStateDesc, &g_wireframeRasterizerState);

	rasterizerStateDesc.FillMode = D3D10_FILL_SOLID;
	g_pd3dDevice->CreateRasterizerState(&rasterizerStateDesc, &g_solidRasterizerState);

	//g_pd3dDevice->RSSetState(g_solidRasterizerState);

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Clean up the objects we've created
//--------------------------------------------------------------------------------------
void CleanupDevice()
{
	CleanupBeforFreeDevice();

	SAFE_RELEASE(g_wireframeRasterizerState);
	SAFE_RELEASE(g_solidRasterizerState);

	SAFE_RELEASE(g_pFont);
	SAFE_RELEASE(g_pDepthStencilView);
	SAFE_RELEASE(g_pDepthStencil);
	SAFE_RELEASE(g_pRenderTargetView);
	SAFE_RELEASE(g_pSwapChain);
	SAFE_RELEASE(g_pd3dDevice);
}


//--------------------------------------------------------------------------------------
// Called every time the application receives a message
//--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message) 
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	case WM_CHAR:
		switch(wParam)           // check what was pressed through wParam
		{
		case '1':
			ChangeSubdivLevel(0);
			SetWindowText(g_hWnd, L"Direct3D 10 GS Subdivision - level 0");
			break;
		case '2':
			ChangeSubdivLevel(1);
			SetWindowText(g_hWnd, L"Direct3D 10 GS Subdivision - level 1");
			break;
		case '3':
			ChangeSubdivLevel(2);
			SetWindowText(g_hWnd, L"Direct3D 10 GS Subdivision - level 2");
			break;
		case 0x1B: // escape key
			exit(-1);
			break;
		case ' ':
			g_isWireFrame = !g_isWireFrame;
			if (g_isWireFrame)
				g_pd3dDevice->RSSetState(g_wireframeRasterizerState);
			else
				g_pd3dDevice->RSSetState(g_solidRasterizerState);
			break;

		}
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}

	return 0;
}


//--------------------------------------------------------------------------------------
// Render the help and statistics text
//--------------------------------------------------------------------------------------
void RenderText()
{
	WCHAR fps[32];
	swprintf_s(fps, 32, L"%0.2f fps ", g_fps);
	RECT rc;
	SetRect(&rc, 2, 0, 0, 0); 
	D3DXCOLOR m_color = D3DXCOLOR(1,1,1,1);

	g_pFont->DrawText(NULL, fps, -1, &rc, DT_NOCLIP, m_color);
	g_pd3dDevice->OMSetDepthStencilState(NULL, 0);  // since DrawText overwrites the BlendState and DepthStencilState -s
}

void UpdateFrameStatistics()
{
	g_fpsRenderedFrames++;	

	LARGE_INTEGER qwTime;
	QueryPerformanceCounter(&qwTime);
	double currentTime = qwTime.QuadPart / (double) g_llQPFTicksPerSec;

	if (currentTime - g_fpsLastUpdateTime > 1.0)   // update in each seconds
	{
		g_fps = g_fpsRenderedFrames / (currentTime - g_fpsLastUpdateTime);
		g_fpsRenderedFrames = 0;
		g_fpsLastUpdateTime = currentTime;
	}

}

//--------------------------------------------------------------------------------------
// Render a frame
//--------------------------------------------------------------------------------------
void RenderFramework()
{
	UpdateFrameStatistics();

	// Clear the back buffer
	float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; // red, green, blue, alpha
	g_pd3dDevice->ClearRenderTargetView(g_pRenderTargetView, ClearColor);

	// Clear the depth stencil
	g_pd3dDevice->ClearDepthStencilView(g_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0, 0);

	//g_pd3dDevice->RSSetState(g_solidRasterizerState);
	RenderSubdiv(g_pd3dDevice);

	//g_pd3dDevice->RSSetState(NULL);
	RenderText();

	// Present our back buffer to our front buffer
	g_pSwapChain->Present(0, 0);
}


